﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;

namespace SequenceAssembler
{
    

    public class BitDeque: Storable
    {        
        private	byte[] data;
	    private	uint start; // in 2-bit
	    private	uint end; // in 2-bit
	    private	int m_capacity; //in byte

	    public BitDeque(uint initBaseCapacity = 1) {
            int initCapacity = (int)((initBaseCapacity + 4) >> 2);
            data = new byte[initCapacity];
		    start = 0;
            end = 0;
            m_capacity = initCapacity;
            Array.Clear(data, 0, initCapacity);
        }

        public void initialize(byte[] kmer, uint k)
	    {
		    ensureCapacity(k);
            Array.Copy(kmer, data, (k + 4) >> 2);
		    start = 0;
            end = (uint)k;
	    }

        public void initialize(long kmer, uint k)
        {
            ensureCapacity(k);
            long bitmask = 3L;
            for (int i = 0; i < k; ++i) {
                byte ch = (byte) (kmer >> (((int)k - 1 - i) << 1) & bitmask);
                push_back(ch);
            }
            start = 0;
            end = (uint)k;
        }

        public void push_back(byte ele)
        {
		    if (needExpand()) { expand(); }
		    data[end >> 2] |= (byte) (ele << (int) ((3 - (end & 3)) << 1));
		    end = (end + 1) % (uint) (m_capacity << 2);
	    }

        public void push_front(byte ele)
        {
		    if (needExpand()) { expand(); }
		    start = ((uint) (m_capacity << 2) + start - 1) % (uint) (m_capacity << 2);
		    data[start >> 2] |= (byte) (ele << (int) ((3 - (start & 3)) << 1));
	    }

	    /**
	     *
	     * @param src 
	     * @param first 
	     * @param exlast the position just exceed the last element to copy
	     */
        public void push_front_range(BitDeque src, uint first, uint exlast)
	    {
		    uint newSize = exlast - first + size();
		    if (newSize + 1 > (m_capacity << 3)) {
			    ensureCapacity(newSize);
		    }
		    for (; first < exlast;) {
			    push_front(src[--exlast]);
		    }
	    }

		public void push_front_all(BitDeque src)
		{
			push_front_range(src, 0, src.size());
		}

	    /**
	     *
	     * @param src 
	     * @param first 
	     * @param exlast the position just exceed the last element to copy
	     */
        public void push_back_range(BitDeque src, uint first, uint exlast)
	    {
		    uint newSize = exlast - first + size();
		    if (newSize + 1 > (m_capacity << 3)) {
			    ensureCapacity(newSize);
		    }
		    for (; first < exlast; ++first) {
			    push_back(src[first]);
		    }
	    }

		public void push_back_all(BitDeque src)
		{
			push_back_range(src, 0, src.size());
		}

        public uint size() { return ((uint)(m_capacity << 2) + end - start) % (uint)(m_capacity << 2); }
        public uint capacity() { return (uint)(m_capacity << 2) - 1; }
        public bool isEmpty() { return start == end; }

        public void clear()
        {
		    Array.Clear(data, 0, m_capacity);
		    start = 0;
		    end = 0;
	    }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="newCapacity">in 2-bit</param>
        public void ensureCapacity(uint newBaseCapacity)
        {
            int newCapacity = (int) ((newBaseCapacity + 4) >> 2);
		    if (newCapacity <= m_capacity) { return; }
		    byte[] newData = new byte[newCapacity];
		    Array.Clear(newData, 0, newCapacity);

		    if (start <= end) {
			    Array.Copy(this.data, newData, m_capacity);
		    } else {
			    Array.Copy(this.data, newData, 1 + (end >> 2));
			    newData[end >> 2] &= (byte) (-1 << (int) ((3 - (end & 3)) << 1));
			    uint newStart = start + (uint) (newCapacity << 2) - (uint) (m_capacity << 2);
			    Array.Copy(this.data, start >> 2, newData, newStart >> 2, m_capacity - (start >> 2));
			    newData[newStart >> 2] &= (byte) ~(-1 << (int) ((4 - (newStart & 3)) << 1));
			    start = newStart;
		    }
		    m_capacity = newCapacity;
		    data = newData;
	    }

	    public byte this[uint pos] {
            get
            {
                Debug.Assert(pos >= 0 && pos < size());
                pos = (pos + start) % (uint) (m_capacity << 2);
                return (byte) ((data[pos >> 2] >> (int) ((3 - (pos & 3)) << 1)) & 3);
            }
	    }

	    private bool needExpand() { return ((m_capacity << 2) + end + 1 - start) % (m_capacity << 2) == 0; }
	    private void expand() {
		    ensureCapacity((uint)m_capacity << 3);
	    }

        public int GetBinarySize() {
            return sizeof(uint) + sizeof(uint) + sizeof(int) + sizeof(byte) * m_capacity;
        }

        public void WriteBinary(BinaryWriter bw) {
            bw.Write(start);
            bw.Write(end);
            bw.Write(m_capacity);
            bw.Write(data);
        }

        public void ReadBinary(BinaryReader br) {
            start = br.ReadUInt32();
            end = br.ReadUInt32();
            m_capacity = br.ReadInt32();
            data = br.ReadBytes(m_capacity);
        }


		public static void unitTest()
		{
			string seq1 = "ACACACACACAC";
			string seq2 = "GTGTGTGTGTGT";

			long l1 = BaseOperation.kmerToLong(seq1);
			long l2 = BaseOperation.kmerToLong(seq2);

			Console.WriteLine(Convert.ToString(l1, 2));
			Console.WriteLine(Convert.ToString(l2, 2));

			BitDeque d1 = new BitDeque();
			BitDeque d2 = new BitDeque();
			d1.initialize(l1, (uint)seq1.Length);
			d2.initialize(l2, (uint)seq2.Length);

			StringBuilder sb1 = new StringBuilder();
			StringBuilder sb2 = new StringBuilder();
			for (uint i = 0; i < d1.size(); ++i)
			{
				sb1.Append(BaseOperation.byteToBase(d1[i]));
			}
			for (uint i = 0; i < d2.size(); ++i)
			{
				sb2.Append(BaseOperation.byteToBase(d2[i]));
			}

			Console.WriteLine(sb1.ToString());
			Console.WriteLine(sb2.ToString());

			d1.push_front(0);
			d1.push_front(0);
			d1.push_front(0);
			d1.push_front(0);
			d1.push_back(1);
			d1.push_back(1);
			d1.push_back(1);
			d1.push_back(1);
			d2.push_front(2);
			d2.push_front(2);
			d2.push_front(2);
			d2.push_front(2);
			d2.push_back(3);
			d2.push_back(3);
			d2.push_back(3);
			d2.push_back(3);

			sb1.Clear();
			sb2.Clear();
			for (uint i = 0; i < d1.size(); ++i)
			{
				sb1.Append(BaseOperation.byteToBase(d1[i]));
			}
			for (uint i = 0; i < d2.size(); ++i)
			{
				sb2.Append(BaseOperation.byteToBase(d2[i]));
			}
			Console.WriteLine(sb1);
			Console.WriteLine(sb2);

			d1.push_front_range(d2, 0, d2.size() / 2);
			d1.push_back_range(d2, d2.size() / 2, d2.size());
			sb1.Clear();
			for (uint i = 0; i < d1.size(); ++i)
			{
				sb1.Append(BaseOperation.byteToBase(d1[i]));
			}

			Console.WriteLine(sb1);
		}
    }
}
